#!/usr/bin/env python

import logging
import os
import sys

from optparse import OptionParser
from stat import *

import wiseguy.wsgi_preforking


class NoSuchApplication(Exception):
  pass


class WSGIRunWrapper(object):
  '''
  a simple insulator so application imports do not taint the application
  prior to forking off the worker children
  '''
  def __init__(self, function_identifier):
    self.function_identifier = function_identifier
    self._wsgi_function = None
    
  @property
  def wsgi_function(self):
    if self._wsgi_function is None:
      self._wsgi_function = get_wsgi_app_function(self.function_identifier)
    return self._wsgi_function

  def __call__(self, environ, start_response):
    try:
      for push in self.wsgi_function(environ, start_response):
        yield push
    except Exception, e:
      logging.exception('error in WSGIRunWrapper')
      status = '500 Internal Server Error'
      response_headers = [('Content-Type', 'text/plain')]
      start_response(status, response_headers, sys.exc_info())
      yield ""
                                            
def init_logging(log_filename, log_level):
  logging.basicConfig(
    level=log_level,
    format='%(asctime)s %(process)d %(levelname)s %(message)s',
    filename=log_filename,
    filemode='a+')

def get_wsgi_app_function(function_identifier):
  try:
    identifier_pieces = function_identifier.split('.')
    function_name = identifier_pieces[-1]
    module_name = '.'.join(identifier_pieces[:-1])
    # __import__ returns the top level package/module
    app_object = __import__(module_name)
    for name in identifier_pieces[1:]:
      app_object = getattr(app_object, name)
    return app_object
  except (ImportError, AttributeError), e:
    logging.exception('get_wsgi_app_function %s failed', function_identifier)
    raise NoSuchApplication(function_identifier)

  
def validate_bind_address(option, opt_str, value, parser):
  try:
    host, port = value.split(':')
    setattr(parser.values, option.dest, (host, int(port)))
  except ValueError:
    raise OptionValueError('%s option invalid' % opt_str)

def validate_log_level(option, opt_str, value, parser):
  try:
    log_level = logging.getLevelName(value.upper())
    setattr(parser.values, option.dest, int(log_level))
  except ValueError:
    raise OptionValueError('%s error, unknown log level: %s' %
                           (opt_str, value))

if __name__ == '__main__':

  parser = OptionParser()
  parser.add_option('--bind-address',
                    action='callback',  callback=validate_bind_address,
                    type='str', nargs=1, default=('127.0.0.1', 4000),
                    help='FCGI TCP host:port or unix domain socket path')
  parser.add_option('--wsgi-app', default=None,
                    help='fully qualified symbol that is WSGI compliant')
  parser.add_option('--management-address',
                    action='callback',  callback=validate_bind_address,
                    type='str', nargs=1,
                    help='HTTP TCP host:port')
  parser.add_option('--max-requests',
                    default=None,
                    type='int',
                    help='max requests handled per child')
  parser.add_option('--max-rss',
                    default=None,
                    type='int',
                    help='max RSS (in kilobytes) before a child gets a SIGTERM')
  parser.add_option('--accept-input-timeout',
                    default=1,
                    type='int',
                    help='timeout in seconds between accept() and read()')
  parser.add_option('--workers',
                    default=1,
                    type='int',
                    help='number of worker processes')
  parser.add_option('--log-level', default=logging.INFO,
                    action='callback', callback=validate_log_level,
                    type='str', nargs=1,
                    help='set the base log level')
  parser.add_option('--profile-path', default=None,
                    help='log hotshot profile data to this path')
  parser.add_option('--profile-uri', default=None,
                    help='profile any uri matching this regex')
  parser.add_option('--log-file', default='./wiseguyd.log')
  parser.add_option('--pid-file', default='./wiseguyd.pid')
  
  (options, args) = parser.parse_args()

  init_logging(options.log_file, options.log_level)
  
  try:
    f = open(options.pid_file, 'w')
    f.write(str(os.getpid()))
    f.close()
    os.chmod(options.pid_file,
             S_IRUSR | S_IRGRP | S_IROTH | S_IWUSR | S_IWGRP | S_IWOTH)
  except Exception, e:
    logging.exception('error writing pid file')
  
  try:
    server = wiseguy.wsgi_preforking.PreForkingWSGIServer(
      WSGIRunWrapper(options.wsgi_app),
      server_address=options.bind_address,
      management_address=options.management_address,
      workers=options.workers,
      max_requests=options.max_requests,
      max_rss=options.max_rss,
      profile_path=options.profile_path,
      profile_uri=options.profile_uri,
      accept_input_timeout=options.accept_input_timeout)
    logging.info('wiseguyd started')
    server.serve_forever()
  except Exception, e:
    logging.exception('wiseguyd aborted')
